#if defined(_MSC_VER)
#pragma once
#endif

#ifndef LM_CORE_TRANSFORM_H
#define LM_CORE_TRANSFORM_H

#include "lm.h"
#include "matrix.h"
#include "ray.h"
#include "bbox.h"


class Transform
{
public:
	Transform() { }
	Transform(const Transform &t) : mat(t.mat), matInv(t.matInv) { }
	Transform(const Matrix4x4 &m) : mat(m), matInv(m.Inverse()) { }
	Transform(const Matrix4x4 &m,const Matrix4x4 &mInv) : mat(m), matInv(mInv) { 
		Assert((mat*matInv).IsIdentity());
	}
	Transform(float * m) {
		mat = Matrix4x4(m);
		matInv = mat.Inverse();
	}
	inline Transform& operator=(const Transform& t) {
		mat = t.mat;
		matInv = t.matInv;
		Assert((mat*matInv).IsIdentity());
		return *this;
	}
	inline bool operator==(const Transform &t) const {
		return t.mat == mat && t.matInv == matInv;
	}
	inline bool operator!=(const Transform &t) const {
		return t.mat != mat || t.matInv != matInv;
	}
	inline Transform Inverse() const {
		return Transform(matInv, mat);
	}
	inline Transform Transpose() const {
		return Transform(mat.Transpose(),matInv.Transpose());
	}
	inline bool IsIdentity() const { return mat.IsIdentity(); }
	inline Transform operator*(const Transform &t) const {
		// m = m1 x m2
		// mInv = m2Inv x m1Inv;
		Matrix4x4 m = mat * t.mat;			
		Matrix4x4 mInv = t.matInv * matInv;
		return Transform(m, mInv);
	}
	inline Transform& operator*=(const Transform &t) {
		mat *= t.mat;
		matInv = t.matInv * matInv;
		return *this;
	}
	// operator()
	inline Point3 operator()(const Point3 &p) const {
		return mat * p;
	}
	inline void operator()(const Point3 &p, Point3 *ptran) const {
		*ptran = mat * p;
	}
	inline Vector3 operator()(const Vector3 &v) const {
		return mat * v;
	}
	inline void operator()(const Vector3 &v, Vector3 *vtran) const {
		*vtran = mat * v;
	}
	inline Vector4 operator()(const Vector4 &v) const {
		return mat * v;
	}
	inline void operator()(const Vector4 &v, Vector4 *vtran) const {
		*vtran = mat * v;
	}
	inline Normal operator()(const Normal &n) const {
		return matInv.Transpose() * n;
	}
	inline void operator()(const Normal &n, Normal *ntran) const {
		*ntran = matInv.Transpose() * n;
	}
	inline Ray operator()(const Ray &ray) const {
		Ray ret = ray;
		(*this)(ret.o, &ret.o);
		(*this)(ret.d, &ret.d);
		// recompute dRcp
		ret.dRcp.x = 1.f / ret.d.x;
		ret.dRcp.y = 1.f / ret.d.y;
		ret.dRcp.z = 1.f / ret.d.z;
		return ret;
	}
	inline void operator()(const Ray &ray, Ray *raytrans) const {
		raytrans->o = operator()(ray.o);
		raytrans->d = operator()(ray.d);
		if(raytrans != &ray) {
			raytrans->time = ray.time;
			raytrans->mint = ray.mint;
			raytrans->maxt = ray.maxt;
			raytrans->depth = ray.depth;
		}
		// recompute dRcp for raytrans
		raytrans->dRcp.x = 1.f / raytrans->d.x;
		raytrans->dRcp.y = 1.f / raytrans->d.y;
		raytrans->dRcp.z = 1.f / raytrans->d.z;
	}
	inline RayDifferential operator()(const RayDifferential &ray) const {
		RayDifferential ret;
		(*this)(Ray(ray), &ret);
		ret.hasDifferentials = ray.hasDifferentials;
		(*this)(ray.rxOrigin, &ret.rxOrigin);
		(*this)(ray.ryOrigin, &ret.ryOrigin);
		(*this)(ray.rxDirection, &ret.rxDirection);
		(*this)(ray.ryDirection, &ret.ryDirection);
		return ret;
	}
	inline void operator()(const RayDifferential &ray, RayDifferential *raytrans) const {
		(*this)(Ray(ray), raytrans);
		raytrans->hasDifferentials = ray.hasDifferentials;
		(*this)(ray.rxOrigin, &raytrans->rxOrigin);
		(*this)(ray.ryOrigin, &raytrans->ryOrigin);
		(*this)(ray.rxDirection, &raytrans->rxDirection);
		(*this)(ray.ryDirection, &raytrans->ryDirection);
	}
	inline BBox operator()(const BBox &b) const {
		const Transform &M = *this;
		BBox ret(        M(Point3(b.pMin.x, b.pMin.y, b.pMin.z)));
		ret = Union(ret, M(Point3(b.pMax.x, b.pMin.y, b.pMin.z)));
		ret = Union(ret, M(Point3(b.pMin.x, b.pMax.y, b.pMin.z)));
		ret = Union(ret, M(Point3(b.pMin.x, b.pMin.y, b.pMax.z)));
		ret = Union(ret, M(Point3(b.pMin.x, b.pMax.y, b.pMax.z)));
		ret = Union(ret, M(Point3(b.pMax.x, b.pMax.y, b.pMin.z)));
		ret = Union(ret, M(Point3(b.pMax.x, b.pMin.y, b.pMax.z)));
		ret = Union(ret, M(Point3(b.pMax.x, b.pMax.y, b.pMax.z)));
		return ret;
	}
	inline void operator()(const BBox &b, BBox *btrans) const {
		const Transform &M = *this;
		*btrans = BBox(	         M(Point3(b.pMin.x, b.pMin.y, b.pMin.z)));
		*btrans = Union(*btrans, M(Point3(b.pMax.x, b.pMin.y, b.pMin.z)));
		*btrans = Union(*btrans, M(Point3(b.pMin.x, b.pMax.y, b.pMin.z)));
		*btrans = Union(*btrans, M(Point3(b.pMin.x, b.pMin.y, b.pMax.z)));
		*btrans = Union(*btrans, M(Point3(b.pMin.x, b.pMax.y, b.pMax.z)));
		*btrans = Union(*btrans, M(Point3(b.pMax.x, b.pMax.y, b.pMin.z)));
		*btrans = Union(*btrans, M(Point3(b.pMax.x, b.pMin.y, b.pMax.z)));
		*btrans = Union(*btrans, M(Point3(b.pMax.x, b.pMax.y, b.pMax.z)));
	}
	bool SwapHandedness() const;
	inline bool HasScale() const {
		float la = (*this)(Vector3(1,0,0)).LengthSqured();
		float lb = (*this)(Vector3(0,1,0)).LengthSqured();
		float lc = (*this)(Vector3(0,0,1)).LengthSqured();
		#define NOT_ONE(x) ((x) < .999f || (x) > 1.001f)
		return NOT_ONE(la) || NOT_ONE(lb) || NOT_ONE(lc);
		#undef NOT_ONE
	}
	std::string ToString() const {
		std::ostringstream oss;
		oss << "Transform :\n";
		oss << mat.ToString() << std::endl;
		oss << matInv.ToString();
		return oss.str();
	}
	Matrix4x4 mat;
	Matrix4x4 matInv;
};
inline void Inverse(Transform *t) {
	Matrix4x4 m = t->mat;
	t->mat = t->matInv;
	t->matInv = m;
}
inline void Transpose(Transform *t) {
	Transpose(&t->mat);
	Transpose(&t->matInv);
}

// Get transform from translation, scaling, rotation, et, al.
Transform Translate(float tx, float ty, float tz);
Transform Translate(const Vector3 &v);
Transform Scale(float sx, float sy, float sz);
Transform RotateX(float degree);
Transform RotateY(float degree);
Transform RotateZ(float degree);
Transform Rotate(float degree, float rx, float ry, float rz);
Transform Rotate(float degree, const Vector3 &v);
// Get transform from local frame
Transform FromFrame(const Vector3 &s, const Vector3 &t, const Vector3 &n);
// Get transform from projection
Transform LookAt(const Point3 &eye, const Point3 &center, const Vector3 &up);
Transform Perspective(float fov, float clipNear, float clipFar);
Transform Orthographic(float clipNear, float clipFar);

#endif	// LM_CORE_TRANSFORM_H